﻿#region Copyright (c) 2010 R. Uittenbosch
/*
* Copyright (C) 2010 R. Uittenbosch
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
* associated documentation files (the "Software"), to deal in the Software without restriction, including 
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
* following conditions:
* 
* The above copyright notice and this permission notice shall be included in all copies or substantial 
* portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 
* EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#endregion

//-----------------------------------------------------------------------
// <copyright file="OAuthAuthorization.cs">
//     Copyright (c) Andrew Arnott. All rights reserved.
//     Copyright (c) Beemway. All rights reserved.
// </copyright>
// <license>
//     Microsoft Public License (Ms-PL http://opensource.org/licenses/ms-pl.html).
//     Contributors may add their own copyright notice above.
// </license>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Text;
using System.Xml;
using OAuth.Net;
using OAuth.Net.Mobile.Common;
using OAuth.Net.Mobile.Consumer;
using OAuth.Net.Mobile.Components;
using System.IO;
using System.Globalization;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Win32;
using LinkedService.Mobile.Utility;

namespace LinkedService.Mobile
{  

  [Serializable]
  public class OAuthDataRepository : IDataRepository, IDataRepositorySecurity
  {

    #region private vars
    /// <summary>
    /// The Key to pass to the server
    /// </summary>
    private string Key { get; set; }

    /// <summary>
    /// The Secret to sign the request
    /// </summary>
    private string Secret { get; set; }

    /// <summary>
    /// The requestoken.
    /// </summary>
    private IToken requestToken { get; set; }

    /// <summary>
    /// The accesstoken.
    /// </summary>
    private IToken accessToken { get; set; }

    /// <summary>
    /// The VerifierCode
    /// </summary>
    private string VerifierCode = string.Empty;

    /// <summary>
    /// The ServiceDefinition of the servicerequest
    /// </summary>
    private OAuthService serviceDefinition = null;

    #endregion

    #region Constructor
    /// <summary>
    /// Create a datarepository given the key and secret
    /// </summary>
    /// <param name="key"></param>
    /// <param name="secret"></param>
    public OAuthDataRepository(string key, string secret)
    {
      CurrentStatus = Status.NoTokens;
      Key           = key;
      Secret        = secret;
      if (string.IsNullOrEmpty(Key) || string.IsNullOrEmpty(secret))
        throw new NotSupportedException("Key and secret should not be empty.");
      SetupLocator();
      LoadTokens();
    }

    /// <summary>
    /// Create a datarepository with the default key and secret
    /// </summary>
    public OAuthDataRepository()
      : this("6B4F4D71696D7648445835595A5475314E675F6A42383239374B3962426B52336D574D497661754E6A324E5377366C6C796951506856536B6839744948734A70".α(),
             "7347695F724C69675A4846582D5442783849666131746645544759644577626773626F57356B656D41396D64562D524C45463267516671435167706E3362776F".α())
    { }

    #endregion

    #region Private properties
    private OAuthService ServiceDefinition
    {
      get
      {
        if (serviceDefinition == null)
        {
          serviceDefinition = OAuthService.Create(
                  new OAuth.Net.Mobile.Consumer.EndPoint(string.Format("{0}{1}", Constants.ApiOAuthBaseUrl, Constants.RequestTokenMethod)),
                  new Uri(string.Format("{0}{1}", Constants.ApiOAuthBaseUrl, Constants.AuthorizeTokenMethod)),
                  new OAuth.Net.Mobile.Consumer.EndPoint(string.Format("{0}{1}", Constants.ApiOAuthBaseUrl, Constants.AccessTokenMethod)),
                  "HMAC-SHA1",
                  new OAuthConsumer(Key, Secret));
        }
        return serviceDefinition;
      }
    }

    private IConfiguration Configuration
    {
      get
      {
        return ServiceLocator.Current.GetInstance<IConfiguration>();
      }
    }

    private IOAuthTokenStore TokenStore
    {
      get
      {
        // Get the token store using the servicelocator
        return ServiceLocator.Current.GetInstance<IOAuthTokenStore>();
      }
    }
    #endregion

    #region LocatorSetup

    /// <summary>
    /// Setup de servicelocator
    /// </summary>
    private void SetupLocator()
    {
      BasicServiceLocator injector = null;
      if (ServiceLocator.Current as BasicServiceLocator != null)
        injector = ServiceLocator.Current as BasicServiceLocator;
      else
        injector = new BasicServiceLocator();
      // TokenStorage
      injector.AddComponent<IOAuthTokenStore, RegistryTokenStore>(typeof(IOAuthTokenStore).ToString(), new RegistryTokenStore(Key, Secret));
      // HMAC Signing of the request using OpenNETCF,
      injector.AddComponent<ISigningProvider, HmacSha1SigningProvider>("signing.provider:HMAC-SHA1", new HmacSha1SigningProvider());
      // The Nonceprovider (GUID)
      injector.AddComponent<INonceProvider, GuidNonceProvider>(typeof(INonceProvider).ToString(), new GuidNonceProvider());     
      ServiceLocator.SetLocatorProvider(() => injector);

    }
    #endregion

    #region ResourceParsing

    /// <summary>
    /// Get the resource container in the request
    /// </summary>
    /// <param name="request"></param>
    /// <param name="requestData"></param>
    /// <returns></returns>
    private Stream GetResource(OAuthRequest request, byte[] requestData)
    {
      OAuthResponse response;
      if (requestData == null)
      {
        // Call the server.
        response = request.GetResource();
      }
      else
      {
        if (Configuration.GetItem<bool>("DumpMessages"))
          Encoding.UTF8.GetString(requestData, 0, requestData.Length).Dump();

        // In case data should be passed to the server, do this via a memory stream
        MemoryStream str = new MemoryStream(requestData);
        response = request.GetResource("text/xml", str);
        str.Close();
      }
       
      // Succesful?
      if (response.HasProtectedResource)
      {
        // Store access token if present
        IToken accessT = response.Token as IToken;
        if (accessT != null)
          accessToken = accessT;
        // Save tokens incase not yet done.
        if (CurrentStatus != Status.AccessTokenOk)
          TokenStore.Add(accessToken);

        // Remember some data
        CurrentStatus = Status.AccessTokenOk;    
        LastStatusCode = response.ProtectedResource.StatusCode;

        if (LastStatusCode != HttpStatusCode.OK)
        {
          // If OnError handling, handle the error
          if (OnError != null)
            OnError(this, new ErrorEventArgs() { Message = response.ProtectedResource.StatusDescription, StatusCode = LastStatusCode });           

        }
        // Return the result
        return response.ProtectedResource.GetResponseStream();
      }
      else
      {
        // Store request token if present
        IToken requestT = response.Token as IToken;
        if (requestT != null)
          requestToken = requestT;

        // Remember the requestToken
        TokenStore.Add(requestToken);

        // Remeber some data
        CurrentStatus = Status.RequestTokenOk;
        LastStatusCode = HttpStatusCode.NoContent;

        // Throw to indicate authorization is required
        throw new LinkedInNotAuthorizedException()
                                        {
                                          AuthorizationUri = ServiceDefinition.BuildAuthorizationUrl(
                                               requestToken,
                                               null)
                                        };
      }
    }

    /// <summary>
    /// Create a new OAuthRequest given the specific url
    /// </summary>
    /// <param name="uriFormat"></param>
    /// <param name="httpMethod"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    private OAuthRequest CreateRequest(
          string uriFormat,
          string httpMethod,
          params string[] args)
    {
      return OAuthRequest.Create(
          new OAuth.Net.Mobile.Consumer.EndPoint(
              string.Format(
                  CultureInfo.InvariantCulture,
                  uriFormat,
                  args), httpMethod),
          ServiceDefinition,
          requestToken,
          accessToken);
    }

    /// <summary>
    /// Parse the resource returned from the server
    /// </summary>
    /// <param name="resource"></param>
    /// <returns></returns>
    private string ParseResource(Stream resource)
    {
      if (resource != null)
      {
        // Read everything
        using (StreamReader r = new StreamReader(resource))
        {
          LastRawContent = r.ReadToEnd();
        }
      }
      // Check StatusCodes, and notify the caller about the success
      if (LastStatusCode == HttpStatusCode.OK)
          if (OnSuccess != null)
            OnSuccess(this, EventArgs.Empty);

      if (Configuration.GetItem<bool>("DumpMessages"))
        LastRawContent.Dump();
      // In case we did not get an Ok, return an empty string.
      return LastStatusCode == HttpStatusCode.OK?LastRawContent:string.Empty;
    }

    /// <summary>
    /// Try to retrieve the  content from the server 
    /// </summary>
    /// <param name="requestUrl"></param>
    /// <param name="requestData"></param>
    /// <param name="httpMethod"></param>
    /// <returns></returns>
    private string RetrieveContent(string requestUrl, byte[] requestData, HttpMethod httpMethod)
    {
      try
      {
        // Create Request
        OAuthRequest req = CreateRequest(requestUrl, httpMethod.ToString());

        // Set the verifiercode
        req.OnBeforeGetAccessToken += new EventHandler<PreAccessTokenRequestEventArgs>((sender, preAccesstokenRequestEventArgs) =>
        {
          preAccesstokenRequestEventArgs.Verifier = VerifierCode;
        });
        // Get the resource, and parse it to string
        return ParseResource(GetResource(req, requestData));
      }
      catch (LinkedInNotAuthorizedException ae)
      {
        throw;       
      }
      catch (LinkedInException le)
      {
        // Error handled?
        if (OnError != null)
          OnError(this, new ErrorEventArgs() { Exception = le });
        else
          throw;
      }
      catch (Exception e)
      {
        // Error handled?
        if (OnError != null)
          OnError(this, new ErrorEventArgs() { Exception = e });
        else
          throw;
      }
      return string.Empty;
    }

    #endregion

    #region IDataRetriever Members

    /// <summary>
    /// EventHandler which fires upon an error
    /// </summary>
    public event ErrorEventHandler OnError;

    /// <summary>
    /// EventHandler which fires upon a successful request
    /// </summary>
    public event EventHandler OnSuccess;

    /// <summary>
    /// Return the last HTTPStatusCode
    /// </summary>
    public HttpStatusCode LastStatusCode { get; private set; }

    /// <summary>
    /// Returns the last content in raw xml
    /// </summary>
    public string LastRawContent { get; private set; }

    /// <summary>
    /// Return the current Status of the Tokens
    /// </summary>
    public Status CurrentStatus { get; private set; }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public To RetrieveContent<To, Ti>(string requestUrl, Ti requestData, HttpMethod httpMethod)
    {
     return RetrieveContent<Ti>(requestUrl, requestData, httpMethod).DeserializeXml<To>();
    }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public To RetrieveContent<To, Ti>(string requestUrl, Ti requestData)
    {
      return RetrieveContent<Ti>(requestUrl, requestData).DeserializeXml<To>();
    }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public string RetrieveContent<Ti>(string requestUrl, Ti requestData, HttpMethod httpMethod)
    {
      return RetrieveContent(requestUrl, requestData.SerializeToXmlByte(), httpMethod);
    }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public string RetrieveContent<Ti>(string requestUrl, Ti requestData)
    {
      return RetrieveContent<Ti>(requestUrl, requestData, HttpMethod.GET);
    }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public To RetrieveContent<To>(string requestUrl, HttpMethod httpMethod)
    {
      return RetrieveContent(requestUrl, null, httpMethod).DeserializeXml<To>();
    }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public To RetrieveContent<To>(string requestUrl)
    {
      return RetrieveContent<To>(requestUrl, HttpMethod.GET);
    }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public string RetrieveContent(string requestUrl, HttpMethod httpMethod)
    {
      return RetrieveContent(requestUrl, null, httpMethod);
    }

    /// <summary>
    /// See IDataRetriever interface
    /// </summary>
    public string RetrieveContent(string requestUrl)
    {
      return RetrieveContent(requestUrl, null, HttpMethod.GET);
    }

    #endregion

    #region IOAuthSecurity interface
    /// <summary>
    /// Verify the RequesToken given the supplied VerifierCode
    /// </summary>
    /// <param name="VerifierCode"></param>
    public void VerifyRequestToken(string VerifierCode)
    {
      this.VerifierCode = VerifierCode;
    }  
    

    #endregion

    #region Try to load Tokens from the TokenStore
    /// <summary>
    /// Load the tokens incase the current status did not retrieve all tokens yet
    /// </summary>
    private void LoadTokens()
    {
      CurrentStatus = Status.NoTokens;
      if (TokenStore != null)
      {
        // Get a stored request token 
        requestToken = TokenStore.Get(TokenType.Request);
        // Get a stored access token.
        accessToken = TokenStore.Get(TokenType.Access);
        // Set the  current status
        CurrentStatus = TokenStore.CurrentStatus;
        if (CurrentStatus != Status.AccessTokenOk)
        {
          requestToken = null;
          accessToken = null;
        }
      }
    }
    #endregion
  }

}
